﻿using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using StyleMVVM.DependencyInjection;
using StyleMVVM.ViewModel;
using StyleMVVM.Data;
#if !DOT_NET
using Windows.Foundation;
using Windows.Foundation.Metadata;
#endif
#if NETFX_CORE
using Windows.Storage.AccessCache;
using Windows.UI.Xaml;
using Windows.UI.Xaml.Controls;

#else
using System.Windows;
using System.Windows.Controls;

#endif

namespace StyleMVVM.View.Regions.Adapters
{
	public sealed class ItemsControlAdapter : IRegionAdapter, IDependencyInjectionContainerAware
	{
		private static string supplemental = typeof(ItemsControlAdapter).FullName;
		private IViewBinderService viewBinder;

		public static void RegisterExport(IDependencyInjectionContainer container)
		{
			container.Register(typeof(ItemsControlAdapter)).
			          As(typeof(IRegionAdapter));
		}

		public Type AddapterType
		{
			get { return typeof(ItemsControl); }
		}

		public IDependencyInjectionContainer Container { get; set; }

		public IEnumerable<object> GetCurrentExports(IRegion region, object control)
		{
			List<object> returnValue = new List<object>();

			foreach (object item in ((ItemsControl)control).Items)
			{
				returnValue.Add(item);
			}

			return returnValue;
		}

#if NETFX_CORE
		public IAsyncOperation<bool> Activate(IRegion region, object control, string newExport)
		{
			return IntrnalActivate(region, (ItemsControl)control, newExport).AsAsyncOperation();
		}

		public IAsyncOperation<bool> Deactivate(IRegion region, object control, string newExport)
		{
			return InternalDeactivate(region, (ItemsControl)control, newExport).AsAsyncOperation();
		}

		public IAsyncOperation<bool> Navigate(IRegion region, object control, string newExport, object navigationParameter)
		{
			return InternalNavigate(region, (ItemsControl)control, newExport, navigationParameter).AsAsyncOperation();
		}

		[DefaultOverload]
		public IAsyncOperation<bool> Add(IRegion region, object control, string newExportName, object navigationParameter)
		{
			return InternalAdd(region, (ItemsControl)control, newExportName, navigationParameter).AsAsyncOperation();
		}

		public IAsyncOperation<bool> Add(IRegion region, object control, object newExport, object navigationParameter)
		{
			return InternalAdd(region, (ItemsControl)control, newExport, navigationParameter).AsAsyncOperation();
		}

		[DefaultOverload]
		public IAsyncOperation<bool> Remove(IRegion region, object control, string oldExportName)
		{
			return InternalRemove(region, (ItemsControl)control, oldExportName).AsAsyncOperation();
		}

		public IAsyncOperation<bool> Remove(IRegion region, object control, object oldExport)
		{
			return InternalRemove(region, (ItemsControl)control, oldExport).AsAsyncOperation();
		}
#else

		public Task<bool> Activate(IRegion region, object control, string newExport)
		{
			return IntrnalActivate(region, (ItemsControl)control, newExport);
		}

		public Task<bool> Deactivate(IRegion region, object control, string newExport)
		{
			return InternalDeactivate(region, (ItemsControl)control, newExport);
		}

		public Task<bool> Navigate(IRegion region, object control, string newExport, object navigationParameter)
		{
			return InternalNavigate(region, (ItemsControl)control, newExport, navigationParameter);
		}

		[DefaultOverload]
		public Task<bool> Add(IRegion region, object control, string newExportName, object navigationParameter)
		{
			return InternalAdd(region, (ItemsControl)control, newExportName, navigationParameter);
		}

		public Task<bool> Add(IRegion region, object control, object newExport, object navigationParameter)
		{
			return InternalAdd(region, (ItemsControl)control, newExport, navigationParameter);
		}

		[DefaultOverload]
		public Task<bool> Remove(IRegion region, object control, string oldExportName)
		{
			return InternalRemove(region, (ItemsControl)control, oldExportName);
		}

		public Task<bool> Remove(IRegion region, object control, object oldExport)
		{
			return InternalRemove(region, (ItemsControl)control, oldExport);
		}
#endif

		private IViewBinderService ViewBinder
		{
			get
			{
				if (viewBinder == null)
				{
					viewBinder = Container.LocateByType(typeof(IViewBinderService)) as IViewBinderService;
				}

				return viewBinder;
			}
		}

		private async Task<bool> InternalNavigate(IRegion region,
		                                          ItemsControl control,
		                                          string newExport,
		                                          object navigationParameter = null)
		{
			bool returnValue = true;
			StyleNavigatingCancelEventArgs cancelEvent = new StyleNavigatingCancelEventArgs();

			for (int i = 0; i < control.Items.Count;)
			{
				object item = control.Items[i];
				FrameworkElement element = item as FrameworkElement;
				INavigationViewModel oldViewModel = null;

				if (element != null)
				{
					oldViewModel = element.DataContext as INavigationViewModel;
				}
				else if (item is INavigationViewModel)
				{
					oldViewModel = item as INavigationViewModel;
				}

				if (oldViewModel != null)
				{
					await oldViewModel.NavigatingFrom(control, cancelEvent);

					if (cancelEvent.Cancel)
					{
						returnValue = false;

						break;
					}
				}

				control.Items.RemoveAt(i);

				if (element != null)
				{
					CallNavigatedFrom(region, element);
				}
			}

			if (returnValue)
			{
				InternalAdd(region, control, newExport, navigationParameter);
			}

			return returnValue;
		}

		private async Task<bool> InternalAdd(IRegion region,
		                                     ItemsControl control,
		                                     string newExportName,
		                                     object navigationParameter = null)
		{
			bool returnValue = false;

			IEnumerable<object> exports = Container.LocateAll(newExportName);

			foreach (object o in exports)
			{
				ViewBinder.Bind(o, navigationParameter);

				control.Items.Add(o);

				DependencyObject oContainer = control.ItemContainerGenerator.ContainerFromItem(o);

				if (oContainer != null)
				{
					BaseRegion.SetExportName(oContainer, newExportName);
				}
			}

			return returnValue;
		}

		private async Task<bool> InternalAdd(IRegion region,
		                                     ItemsControl control,
		                                     object newExport,
		                                     object navigationParameter = null)
		{
			bool returnValue = false;
			IEnumerable addEnumerable = newExport as IEnumerable;

			if (addEnumerable == null)
			{
				addEnumerable = new List<object> { newExport };
			}

			foreach (object o in addEnumerable)
			{
				ViewBinder.Bind(o, navigationParameter);

				control.Items.Add(o);
			}

			return returnValue;
		}

		private async Task<bool> InternalRemove(IRegion region, ItemsControl control, string oldExportName)
		{
			bool returnValue = true;
			StyleNavigatingCancelEventArgs cancelEvent = new StyleNavigatingCancelEventArgs();

			for (int i = 0; i < control.Items.Count;)
			{
				DependencyObject oContainer =
					control.ItemContainerGenerator.ContainerFromItem(control.Items[i]);

				if (BaseRegion.GetExportName(oContainer) == oldExportName)
				{
					object item = control.Items[i];
					FrameworkElement element = item as FrameworkElement;
					INavigationViewModel oldViewModel = null;

					if (element != null)
					{
						oldViewModel = element.DataContext as INavigationViewModel;
					}
					else if (item is INavigationViewModel)
					{
						oldViewModel = item as INavigationViewModel;
					}

					if (oldViewModel != null)
					{
						await oldViewModel.NavigatingFrom(control, cancelEvent);

						if (cancelEvent.Cancel)
						{
							returnValue = false;

							break;
						}
					}

					control.Items.RemoveAt(i);

					if (element != null)
					{
						CallNavigatedFrom(region, element);
					}
				}
				else
				{
					i++;
				}
			}

			return returnValue;
		}

		private async Task<bool> InternalRemove(IRegion region, ItemsControl control, object oldExport)
		{
			bool returnValue = true;
			StyleNavigatingCancelEventArgs cancelEvent = new StyleNavigatingCancelEventArgs();

			if (oldExport is IEnumerable)
			{
				foreach (object o in oldExport as IEnumerable)
				{
					FrameworkElement element = o as FrameworkElement;
					INavigationViewModel oldViewModel = null;

					if (element != null)
					{
						oldViewModel = element.DataContext as INavigationViewModel;
					}
					else if (o is INavigationViewModel)
					{
						oldViewModel = o as INavigationViewModel;
					}

					if (oldViewModel != null)
					{
						await oldViewModel.NavigatingFrom(control, cancelEvent);

						if (cancelEvent.Cancel)
						{
							returnValue = false;

							break;
						}
					}

					control.Items.Remove(o);

					if (element != null)
					{
						CallNavigatedFrom(region, element);
					}
				}
			}
			else
			{
				for (int i = 0; i < control.Items.Count;)
				{
					DependencyObject oContainer =
						control.ItemContainerGenerator.ContainerFromItem(control.Items[i]);

					if (control.Items[i] == oldExport)
					{
						object item = control.Items[i];
						FrameworkElement element = item as FrameworkElement;
						INavigationViewModel oldViewModel = null;

						if (element != null)
						{
							oldViewModel = element.DataContext as INavigationViewModel;
						}
						else if (item is INavigationViewModel)
						{
							oldViewModel = item as INavigationViewModel;
						}

						if (oldViewModel != null)
						{
							await oldViewModel.NavigatingFrom(control, cancelEvent);

							if (cancelEvent.Cancel)
							{
								returnValue = false;

								break;
							}
						}

						control.Items.RemoveAt(i);

						if (element != null)
						{
							CallNavigatedFrom(region, element);
						}
					}
					else
					{
						i++;
					}
				}
			}

			return returnValue;
		}

		private async Task<bool> IntrnalActivate(IRegion region, ItemsControl contorl, string newExport)
		{
			bool returnValue = false;

			foreach (object o in contorl.Items)
			{
				UIElement uiElement = o as UIElement;

				if (uiElement != null)
				{
					if (BaseRegion.GetExportName(uiElement) == newExport)
					{
						uiElement.Visibility = Visibility.Visible;

						returnValue = true;
					}
				}
			}

			return returnValue;
		}

		private async Task<bool> InternalDeactivate(IRegion region, ItemsControl contorl, string newExport)
		{
			bool returnValue = false;

			foreach (object o in contorl.Items)
			{
				UIElement uiElement = o as UIElement;

				if (uiElement != null)
				{
					if (BaseRegion.GetExportName(uiElement) == newExport)
					{
						uiElement.Visibility = Visibility.Collapsed;

						returnValue = true;
					}
				}
			}

			return returnValue;
		}

		private async Task<bool> CallNavigatingFrom(IRegion region, FrameworkElement oldExport)
		{
			if (oldExport != null && oldExport.DataContext is INavigationViewModel)
			{
				INavigationViewModel navigationViewModel = oldExport.DataContext as INavigationViewModel;

				StyleNavigatingCancelEventArgs args = new StyleNavigatingCancelEventArgs();

				await navigationViewModel.NavigatingFrom(oldExport, args);

				return args.Cancel;
			}

			return false;
		}

		private void CallNavigatedTo(IRegion region, FrameworkElement newExport, object navigationParameter)
		{
			if (newExport != null)
			{
				IRegionAware regionAware = newExport.DataContext as IRegionAware;

				if (regionAware != null)
				{
					regionAware.Region = region;
				}

				if (newExport.DataContext is INavigationViewModel)
				{
					INavigationViewModel navigationViewModel = newExport.DataContext as INavigationViewModel;

					navigationViewModel.NavigationParameter = navigationParameter;

					navigationViewModel.NavigatedTo(newExport, new StyleNavigationEventArgs());
				}
			}
		}

		private void CallNavigatedFrom(IRegion region, FrameworkElement oldExport)
		{
			if (oldExport != null)
			{
				IRegionAware regionAware = oldExport.DataContext as IRegionAware;

				if (regionAware != null)
				{
					regionAware.Region = region;
				}

				INavigationViewModel navigationViewModel = oldExport.DataContext as INavigationViewModel;

				if (navigationViewModel != null)
				{
					navigationViewModel.NavigatedFrom(oldExport, null);
				}
			}
		}
	}
}